package tw.com.hitevision.whiteboard.android.widgets.floatView.widget.appfloat

import android.animation.Animator
import android.annotation.SuppressLint
import android.app.Service
import android.content.Context
import android.graphics.PixelFormat
import android.graphics.Rect
import android.os.Build
import android.view.*
import tw.com.hitevision.whiteboard.android.widgets.floatView.anim.AppFloatAnimatorManager
import tw.com.hitevision.whiteboard.android.widgets.floatView.data.FloatConfig
import tw.com.hitevision.whiteboard.android.widgets.floatView.enums.ShowPattern
import tw.com.hitevision.whiteboard.android.widgets.floatView.interfaces.OnFloatTouchListener
import tw.com.hitevision.whiteboard.android.widgets.floatView.utils.DisplayUtils
import tw.com.hitevision.whiteboard.android.widgets.floatView.utils.LifecycleUtils
import tw.com.hitevision.whiteboard.android.widgets.floatView.utils.Logger

internal class AppFloatManager(val context: Context, var config: FloatConfig) {

    lateinit var windowManager: WindowManager
    lateinit var params: WindowManager.LayoutParams
    var frameLayout: ParentFrameLayout? = null
    private lateinit var touchUtils: TouchUtils

    /**
     *  create Floating view
     */
    fun createFloat() = try {
        touchUtils = TouchUtils(context, config)
        initParams()
        addView()
        config.isShow = true
    } catch (e: Exception) {
        config.callbacks?.createdResult(false, "$e", null)
        config.floatCallbacks?.builder?.createdResult?.invoke(false, "$e", null)
    }

    private fun initParams() {
        windowManager = context.getSystemService(Service.WINDOW_SERVICE) as WindowManager
        params = WindowManager.LayoutParams().apply {
            // after android 6.0, windowManger type must be use TYPE_APPLICATION_OVERLAY
            type =
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY else WindowManager.LayoutParams.TYPE_PHONE
            format = PixelFormat.RGBA_8888
            gravity = Gravity.START or Gravity.TOP
            // no auto forces
            flags =
                WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
            width =
                if (config.widthMatch) WindowManager.LayoutParams.MATCH_PARENT else WindowManager.LayoutParams.WRAP_CONTENT
            height =
                if (config.heightMatch) WindowManager.LayoutParams.MATCH_PARENT else WindowManager.LayoutParams.WRAP_CONTENT

            // position
            if (config.locationPair != Pair(0, 0)) {
                x = config.locationPair.first
                y = config.locationPair.second
            }
        }
    }

    /**
     *  addView
     */
    private fun addView() {
        // create parentLayout
        frameLayout = ParentFrameLayout(context, config)
        frameLayout?.tag = config.floatTag

        val floatingView = LayoutInflater.from(context).inflate(config.layoutId!!, frameLayout, true)

        // avoid flashing when create floating view(Do not set visible gone or position have problem)
        floatingView.visibility = View.INVISIBLE
        // add frameLayout to windowManager
        windowManager.addView(frameLayout, params)

        // implement drag effect
        frameLayout?.touchListener = object : OnFloatTouchListener {
            override fun onTouch(event: MotionEvent) =
                touchUtils.updateFloat(frameLayout!!, event, windowManager, params)
        }

        // render floating view finish, set init position and execute enter animation
        frameLayout?.layoutListener = object : ParentFrameLayout.OnLayoutListener {
            override fun onLayout() {
                setGravity(frameLayout)
                config.apply {
                    // if filter current activity that execute enter animation
                    if (filterSelf
                        || (showPattern == ShowPattern.BACKGROUND && LifecycleUtils.isForeground())
                        || (showPattern == ShowPattern.FOREGROUND && !LifecycleUtils.isForeground())
                    ) setVisible(View.GONE) else enterAnim(floatingView)

                    // set callbacks
                    layoutView = floatingView
                    invokeView?.invoke(floatingView)
                    callbacks?.createdResult(true, null, floatingView)
                    floatCallbacks?.builder?.createdResult?.invoke(true, null, floatingView)
                }
            }
        }
    }

    /**
     *  set floating view gravity, Default Gravity.LEFT and Gravity.TOP
     */
    @SuppressLint("RtlHardcoded")
    private fun setGravity(view: View?) {
        if (config.locationPair != Pair(0, 0) || view == null) return
        val parentRect = Rect()

        windowManager.defaultDisplay.getRectSize(parentRect)
        val parentBottom = parentRect.bottom - DisplayUtils.statusBarHeight(view)
        when (config.gravity) {

            Gravity.END, Gravity.END or Gravity.TOP, Gravity.RIGHT, Gravity.RIGHT or Gravity.TOP ->
                params.x = parentRect.right - view.width

            Gravity.START or Gravity.BOTTOM, Gravity.BOTTOM, Gravity.LEFT or Gravity.BOTTOM ->
                params.y = parentBottom - view.height

            Gravity.END or Gravity.BOTTOM, Gravity.RIGHT or Gravity.BOTTOM -> {
                params.x = parentRect.right - view.width
                params.y = parentBottom - view.height
            }

            Gravity.CENTER -> {
                params.x = (parentRect.right - view.width).shr(1)
                params.y = (parentBottom - view.height).shr(1)
            }

            Gravity.CENTER_HORIZONTAL, Gravity.TOP or Gravity.CENTER_HORIZONTAL ->
                params.x = (parentRect.right - view.width).shr(1)

            Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL -> {
                params.x = (parentRect.right - view.width).shr(1)
                params.y = parentBottom - view.height
            }

            Gravity.CENTER_VERTICAL, Gravity.START or Gravity.CENTER_VERTICAL, Gravity.LEFT or Gravity.CENTER_VERTICAL ->
                params.y = (parentBottom - view.height).shr(1)

            Gravity.END or Gravity.CENTER_VERTICAL, Gravity.RIGHT or Gravity.CENTER_VERTICAL -> {
                params.x = parentRect.right - view.width
                params.y = (parentBottom - view.height).shr(1)
            }
            // else Default
            else -> {
            }
        }

        // set offsets
        params.x += config.offsetPair.first
        params.y += config.offsetPair.second
        // update position
        windowManager.updateViewLayout(view, params)
    }

    /**
     * set floating view visible
     */
    fun setVisible(visible: Int, needShow: Boolean = true) {
        if (frameLayout == null || frameLayout!!.childCount < 1) return
        // if hide the needShow is false
        config.needShow = needShow
        frameLayout!!.visibility = visible
        val view = frameLayout!!.getChildAt(0)
        if (visible == View.VISIBLE) {
            config.isShow = true
            config.callbacks?.show(view)
            config.floatCallbacks?.builder?.show?.invoke(view)
        } else {
            config.isShow = false
            config.callbacks?.hide(view)
            config.floatCallbacks?.builder?.hide?.invoke(view)
        }
    }

    /**
     *  enter animation
     */
    private fun enterAnim(floatingView: View) {
        if (frameLayout == null || config.isAnim) return
        val manager: AppFloatAnimatorManager? =
            AppFloatAnimatorManager(frameLayout!!, params, windowManager, config)
        val animator: Animator? = manager?.enterAnim()
        if (animator != null) {
            params.flags =
                WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
            animator.addListener(object : Animator.AnimatorListener {
                override fun onAnimationRepeat(animation: Animator?) {}

                override fun onAnimationEnd(animation: Animator?) {
                    config.isAnim = false
                    params.flags =
                        WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                }

                override fun onAnimationCancel(animation: Animator?) {}

                override fun onAnimationStart(animation: Animator?) {
                    floatingView.visibility = View.VISIBLE
                    config.isAnim = true
                }
            })
//            animator.start()
        } else {
            floatingView.visibility = View.VISIBLE
            windowManager.updateViewLayout(floatingView, params)
        }
    }

    /**
     *  exit animation
     */
    fun exitAnim() {
        if (frameLayout == null || config.isAnim) return
        floatOver()
//        val manager: AppFloatAnimatorManager? =
//            AppFloatAnimatorManager(frameLayout!!, params, windowManager, config)
//        val animator: Animator? = manager?.exitAnim()
//        if (animator == null) floatOver() else {
//            params.flags =
//                WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
//            animator.addListener(object : Animator.AnimatorListener {
//                override fun onAnimationRepeat(animation: Animator?) {}
//
//                override fun onAnimationEnd(animation: Animator?) = floatOver()
//
//                override fun onAnimationCancel(animation: Animator?) {}
//
//                override fun onAnimationStart(animation: Animator?) {
//                    config.isAnim = true
//                }
//            })
//            animator.start()
//        }
    }

    /**
     *  exit without exit animation
     */
    private fun floatOver() = try {
        config.isAnim = false
        FloatManager.remove(config.floatTag)
        windowManager.removeView(frameLayout)
    } catch (e: Exception) {
        Logger.e("close floating view exception：$e")
    }
}